MyBatis 的秘密(七)反射

MyBatis作为一款ORM框架,主要通过XML定义Object,这就难免用到反射,虽然JDK自带的反射已经方便使用,但是MyBatis依然结合业务功能,将反射功能封装成一个更加易用的包,这个包就在reflection中。


在解析MyBatis的反色包之前,我们需要先通过需要看看MyBatis需要通过反射实现哪些功能。

MyBatis中,可以方便的获取数组属性和对象属性。比如:student.names[0].firstName

因此,MyBatis将反射获取值分为了两类:

  • 容器类
  • POJO

容器类(Collection)通过下标进行取值,而POJO则是通过getter()/setter()进行取值


一个POJO对象拥有两类属性,对象属性以及类信息,在MyBatis中分别通过通过MetaObjectMetaClass对应上述信息。


明白了上述信息,就能理解下面MyBatis反射包中几个关键类的作用:

  • MetaClass : 保存了POJO的类相关信息,比如拥有的方法hasGetter()/hasSetter()
  • MetaObject : 保存了POJO对象的相关的信息,比如通过getter()获取值,通过setter()设置值
  • ObjectWrapper : 用来区别不同的POJO获取属性的不同的方式,比如数组通过索引获取:nums[index],Map通过key获取,POJO通过getter()获取
  • Relector: 这个类便是MyBatis的反射底层类,它简单的封装了JDK底层的反射,其他类都是调用此类进行反射操作。

接下来开始分析MyBatis的源码。

relection包中,包含有factory,invoker,property,wrapper 4个包,

  • factory : 工厂类,主要用来通过反射创建类
  • invoker: 对POJO的各种类的封装以及缓存,方便直接调用
  • property : 设置/获取POJO属性的帮助类
  • wrapper : POJO的包装类,只要需要操作POJO的属性,即可通过此类包装后调用

MyBatis中,主要通过reflection.SystemMetaObject作为简单工厂使用:

public final class SystemMetaObject {
    // 默认创建POJO的工厂类
    public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    // 创建BeanWrapper的工厂类
    public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    // null 对应的Object包装类
    public static final MetaObject NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());

    private SystemMetaObject() {
        // Prevent Instantiation of Static Class
    }
    private static class NullObject {}

    //简单工厂方法 用来创建MetaObject
    public static MetaObject forObject(Object object) {
        return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
    }
}

可以看到,这里是直接返回的MetaObject

MetaObject

MetaObject主要有如下方法:

  • getSetterNames() : 获取该class所有的setter name
  • getGetterNames(): 获取该class所有的getter name
  • hasGetter() : 此class是否有该属性的getter()
  • hasSetter(): 此class是否有该属性的setter()
  • getValue(): 获取对应属性对应的值
  • setValue() : 设置此属性为对应的值

从这里可以看出来,MetaObject基本包含了MyBatis需要使用的所有功能,可以说通过此类,基本连接了XMLPOJO

MetaObject代码中,基本上都是调用的objectWrapper的方法,而MetaObject主要作用在于根据类型创建对应的ObjectWrapper,类似简单工厂,其主要逻辑在构造方法:

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    //如果原本的对象本身就是ObjectWrapper ,那么直接赋值
    if (object instanceof ObjectWrapper) {
        this.objectWrapper = (ObjectWrapper) object;
    } 
    //如果wrapper工厂能够包装此`object`,则直接交给工厂处理
    else if (objectWrapperFactory.hasWrapperFor(object)) {
        this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } 
    //如果对象是Map 则直接使用`MapWrapper`处理
    else if (object instanceof Map) {
        this.objectWrapper = new MapWrapper(this, (Map) object);
    } 
    //如果对象属于collection,则使用`CollectionWrapper`处理
    else if (object instanceof Collection) {
        this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } 
    //否则,使用`BeanWrapper`包装此对象
    else {
        this.objectWrapper = new BeanWrapper(this, object);
    }
}

这里有个不合理的地方,查看CollectionWrapper 的源码可以看到,CollectionWrapper中全是抛出异常。也就是如果传入的是List之类的对象,调用任何方法都会抛出异常。

结合MyBatis整个源码可以发现,MyBatis会处理List ,Array等,然后将其包装到StrictMap中:

 private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("array", object);
      return map;
    }
    return object;
  }

Map中会有逻辑处理Collection

MetaObject中还有一个重要逻辑,前面说过,可能会解析类似student.names[0].firstName的属性,这样就涉及到一个循环处理,并且第一个是POJO,第二个是集合。

MyBatis中,通过递归调用来循环解决:

public Object getValue(String name) {
    //创建属性解析
    PropertyTokenizer prop = new PropertyTokenizer(name);
    //如果属性中包含需要解决的子属性
    if (prop.hasNext()) {
        //再次创建 `MetaObject` 
        MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
        if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
            return null;
        } else {
            //递归调用`MetaObject#getValue() 
            return metaValue.getValue(prop.getChildren());
        }
    } else {
        //通过`ObjectWrapper`获取属性
        return objectWrapper.get(prop);
    }
}

其实这样写倒比较难看懂,也不知道为什么不直接使用foreach,而且PropertyTokenizer也实现了Iterator接口

getValue之所以要放在这一层,便是因为能处理student.names[0].firstName,每一层递归都会新建一个MetaObject对象,然后根据子对象的类型再次通过对应类型的ObjectWrapper去获取值,直到获取到最终类型的值。


看完了MetaObject,接下来看MetaObject中所包装的ObjectWrapper

ObjectWrapperMyBatis的实现主要分两类:BeanWrapperMapWrapper

其中这两个Wrapper都继承自BaseWrapperBaseWrapper中包含了一些通用的方法:

  • resolveCollection(): 通过key 或者getter()获取collection
  • getCollectionValue(): 通过下标,获取数组或容器的指定下标的值
  • setCollectionValue() : 通过下标,设置数组或者容器的值

ObjectWrapper

ObjectWrapper接口所拥有的方法和MetaObject的方法差不多,只不过其实现类分几种。

MapWrapper#get()

  @Override
  public Object get(PropertyTokenizer prop) {
    //判断表达式中是否包含类似list[1]符号
    if (prop.getIndex() != null) {
      //如果有,则先从Map中获取此集合
      Object collection = resolveCollection(prop, map);
      //从集合中获取指定下标的元素  
      return getCollectionValue(prop, collection);
    } else {
      //否则,通过使用map的key获取参数   
      return map.get(prop.getName());
    }
  }

BeanWrapper#get()

@Override
public Object get(PropertyTokenizer prop) {
    //判断表达式中是否包含类似list[1]符号
    if (prop.getIndex() != null) {
        //如果有,则先从现在的POJO中获取此集合
        Object collection = resolveCollection(prop, object);
        return getCollectionValue(prop, collection);
    } else {
        //否则,通过`getter`获取属性的值
        return getBeanProperty(prop, object);
    }
}

接下来看通过getBeanProperty()获取属性的具体方法:

BeanWrapper#getBeanProperty()

  private Object getBeanProperty(PropertyTokenizer prop, Object object) {   
      //获取对应方法的Invoker  
      Invoker method = metaClass.getGetInvoker(prop.getName());
      //通过Invoker调用对应的方法
      return method.invoke(object, NO_ARGUMENTS);

  }

可以看到,BeanWrapper底层有一部分是通过MetaClass获取的信息

接下来看MetaCalss的方法:MetaClass可以类比Java中的Class,主要保存了Class的各种信息,按道理来说应该做一个缓存将MetaClass缓存起来,但是在MyBatis中缓存的是Refector,差别不大、


MetaClass

MetaClass主要包含以下方法:

  • findProperty() : 查找属性,主要用于查找映射数据库的字段和POJO的属性
  • getSetterType() : 获取对应setter需要的字段类型
  • getGetterType() : 获取对应getter返回的字段类型
  • hasSetter() : 是否存在对应属性的setter
  • hasGetter(): 是否存在对应属性的getter
  • get*Invoker() : 获取对应方法的Invoker,获取后可以直接调用

可以看到,都是涉及到class的一些属性。

MetaClass主要功能为分析PropertyTokenizer然后底层调用Reflector

比如:

public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    //判断是否有子节点需要解析
    if (prop.hasNext()) {
       //首先判断本身是否有getter,只有本身存在getter,才能获取到子节点 
      if (reflector.hasSetter(prop.getName())) {
        //创建子节点`MetaClass`   
        MetaClass metaProp = metaClassForProperty(prop.getName());
        //递归处理子节点
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      //通过reflector获取setter  
      return reflector.hasSetter(prop.getName());
    }
  }

Reflector

Reflector主要通过简单工厂创建:

DefaultReflectorFactory#findForClass()

  @Override
  public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return reflectorMap.computeIfAbsent(type, Reflector::new);
    } else {
      return new Reflector(type);
    }
  }

主要是为了能够缓存已经解析的信息

Reflector主要成员变量如下:

//类型信息
private final Class<?> type;
//可读的属性(有getter)
private final String[] readablePropertyNames;
//可写的属性(有setter)
private final String[] writablePropertyNames;
//set对应的Invoker
private final Map<String, Invoker> setMethods = new HashMap<>();
//get对应的Invoker
private final Map<String, Invoker> getMethods = new HashMap<>();
//属性setter需要的类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
//属性getter返回的类型
private final Map<String, Class<?>> getTypes = new HashMap<>();
//默认构造方法
private Constructor<?> defaultConstructor;

对于Reflector,主要便是通过反射处理对应的Class,然后将获取的属性填充到上面的成员变量中。方法不一一分析,这里举例分析一个:

Reflector#getClassMethods()

private Method[] getClassMethods(Class<?> clazz) {
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    //循环处理当前类,然后处理其父类,不处理`Object`
    while (currentClass != null && currentClass != Object.class) {
        //首先将当前类的所有方法加入到容器中
        //方法中主要为Method生成一个唯一的签名
        addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());


        //将当前类对应实现的接口添加到容器中 
        Class<?>[] interfaces = currentClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            addUniqueMethods(uniqueMethods, anInterface.getMethods());
        }
        //处理父类
        currentClass = currentClass.getSuperclass();
    }


    Collection<Method> methods = uniqueMethods.values();

    return methods.toArray(new Method[0]);
}

这个方法是比较简单的方法,但是Reflector的逻辑远远不是这么简单,这其中会涉及到很多细节的语法处理,比如在子类重写父类方法中,返回类型可能不同,这个时候应该以子类的为准等等。。


到这里,MyBatis的反射类基本分析完毕,其实看起来这么复杂,但是只要理解了MyBatis对反射的需求,看起来就不难明白了。

在整个解析过程中,起着最大的作用的类是:PropertyTokenizer

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  //以下属性以`student.names[0].firstName` 为例

  //student  
  private String name;
  //names  
  private final String indexedName;
  //0 
  private String index;
  //names
  private final String children;

  public PropertyTokenizer(String fullname) {
     //通过点判断是否有子表达式
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      //当前表达式  
      name = fullname.substring(0, delim);
      //找到子表达式  
      children = fullname.substring(delim + 1);
    } else {
      name = fullname;
      children = null;
    }
    indexedName = name;
    //通过[找到索引取值表达式  
    delim = name.indexOf('[');
    if (delim > -1) {
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }
}

这个类就是分析student.names[0].firstName这个取值的关键类,并且这个类实现了Iterator接口,因此可以直接使用foreach便利,但是MyBatis的代码并没有这样做,而是使用了递归。


总结

这里再次复习一下,MyBatis的反射使用入口是SystemMetaObject,它包含了整个反射包的基本配置。

MyBatis的代码中,很少能看到静态类,静态工具方法等,基本都是通过对象“注入”到对应的其他对象中。这样当需要修改实现的时候,就能很好的维护。

MyBatis中,首先使用MetaObjectMetaObject内部充当了一个简单工厂方法,用来分辨是处理Map还是处理List还是Bean

ObjectWrapper根据不同的实现,使用不同的方式获取底层的值,对于Bean来说,使用的是MetaClass

MetaClass包含了对应BeanClass的各种元属性。其中MetaClass底层使用的Reflector对象

Reflector对象便是真正的反射实现者,其内部根据传入的Class,生成了各种信息,并且由于Class是通用的,因此MyBatis使用ReflectorFactory将其缓存起来。